/*
 * Copyright (c) 2013, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 *  ======== Mmu.c ========
 */

#include <xdc/std.h>
#include <string.h>
#include <xdc/runtime/Assert.h>
#include <xdc/runtime/Startup.h>
#include <ti/sysbios/family/arm/a15/Cache.h>
#include <ti/sysbios/hal/Hwi.h>

#include "package/internal/Mmu.xdc.h"

#define LEVEL2_BLOCK_DESC_SHIFT 21
#define LEVEL1_TABLE_DESC_SHIFT 30

/*
 *  ======== Mmu_Module_startup ========
 */
Int Mmu_Module_startup(Int phase)
{
    UInt idx;

    /* disable the MMU */
    Mmu_disable();

    /* zero out the descriptor table entries */
    memset(Mmu_module->firstLevelTableBuf, 0,
           Mmu_NUM_LEVEL1_ENTRIES * 8);

    for(idx = 0; idx < Mmu_NUM_LEVEL1_ENTRIES; idx++) {
        memset(Mmu_module->secondLevelTableBuf[idx], 0,
               Mmu_NUM_LEVEL2_ENTRIES * 8);
    }

    /*  
     *  Initialize the descriptor table based upon static config
     *  This function is generated by the Mmu.xdt file.
     */
    Mmu_initFirstLevelTableBuf(Mmu_module->firstLevelTableBuf,
                               Mmu_module->secondLevelTableBuf);

    Mmu_initSecondLevelTableBuf(Mmu_module->secondLevelTableBuf);

    Mmu_init();

    for (idx = 0; idx < 8; idx++) {
        Mmu_writeMAIRAsm(idx, Mmu_module->mairRegAttr[idx]);
    }

    if (Mmu_enableMMU) {        
        /* enable the MMU */
        Mmu_enable();
    }
    
    return (Startup_DONE);
}

/*
 *  ======== Mmu_disable ========
 *  Function to disable the MMU.
 */
Void Mmu_disable()
{
    UInt16 type;
    UInt   key;

    /* if MMU is alreay disabled, just return */
    if (!(Mmu_isEnabled())) {
        return;
    }
    
    key = Hwi_disable();

    /* get the current enabled bits */
    type = Cache_getEnabled();
    
    if (type & Cache_Type_L1D) {
        /* writeback invalidate all data cache */ 
        Cache_wbInvAll();
        
        /* drain the write buffer */
        Cache_wait();
        
        /* disable the L1 data cache */
        Cache_disable(Cache_Type_L1D);
    }
    
    if (type & Cache_Type_L1P) {
        /* invalidate all L1 program cache */
        Cache_invL1pAll();

        /* disable L1P cache */
        Cache_disable(Cache_Type_L1P);
    }
    
    /* disables the MMU */
    Mmu_disableAsm();

    /* set cache back to initial settings */
    Cache_enable(type);

    Hwi_restore(key);
}

/*
 *  ======== Mmu_enable ========
 *  Function to enable the MMU.
 */
Void Mmu_enable()
{
    UInt16 type;
    UInt   key;

    /* if MMU is already enabled then just return */
    if (Mmu_isEnabled()) {
        return;
    }

    key = Hwi_disable();

    /* get the current enabled bits */
    type = Cache_getEnabled();
    
    if (type & Cache_Type_ALLP) {
        /* invalidate all of L1 program cache */
        Cache_invL1pAll();

        /* disable L1P cache */
        Cache_disable(Cache_Type_ALLP);
    }
    
    /* enables the MMU */
    Mmu_enableAsm();
    
    /* set cache back to initial settings */
    Cache_enable(type);

    Hwi_restore(key);
}

/*
 *  ======== Mmu_setMAIR ========
 */
Void Mmu_setMAIR(UInt attrIndx, UInt attr)
{
    /* Update module state mairRegAttr */
    Mmu_module->mairRegAttr[attrIndx] = attr;

    /* Write attr to MAIRn register */
    Mmu_writeMAIRAsm(attrIndx, attr);
}

/*
 *  ======== Mmu_initDescAttrs ========
 */
Void Mmu_initDescAttrs(Mmu_DescriptorAttrs *attrs)
{
    /* Assert that attrs != NULL */
    Assert_isTrue(attrs != NULL, Mmu_A_nullPointer);
    
    attrs->type = Mmu_defaultAttrs.type;
    attrs->nsTable = Mmu_defaultAttrs.nsTable;
    attrs->apTable = Mmu_defaultAttrs.apTable;
    attrs->xnTable = Mmu_defaultAttrs.xnTable;
    attrs->pxnTable = Mmu_defaultAttrs.pxnTable;
    attrs->noExecute = Mmu_defaultAttrs.noExecute;
    attrs->privNoExecute = Mmu_defaultAttrs.privNoExecute;
    attrs->contiguous = Mmu_defaultAttrs.contiguous;
    attrs->notGlobal = Mmu_defaultAttrs.notGlobal;
    attrs->accessFlag = Mmu_defaultAttrs.accessFlag;
    attrs->shareable = Mmu_defaultAttrs.shareable;
    attrs->accPerm = Mmu_defaultAttrs.accPerm;
    attrs->nonSecure = Mmu_defaultAttrs.nonSecure;
    attrs->attrIndx = Mmu_defaultAttrs.attrIndx;
    attrs->reserved = Mmu_defaultAttrs.reserved;
}

/*
 *  ======== Mmu_setFirstLevelDesc ========
 */
Void Mmu_setFirstLevelDesc(Ptr virtualAddr, UInt64 phyAddr,
                             Mmu_DescriptorAttrs *attrs)
{
    UInt32 index = (UInt32)virtualAddr >> LEVEL1_TABLE_DESC_SHIFT;
    UInt64 desc = 0;
    Bool   enabled;
    
    /* Assert that attrs != NULL */
    Assert_isTrue(attrs != NULL, Mmu_A_nullPointer);
    
    /* determine the current state of the MMU */
    enabled = Mmu_isEnabled();

    /* disable the MMU (if already disabled, does nothing) */
    Mmu_disable();

    /* Determine which kind of descriptor. */
    switch (attrs->type) {
        case Mmu_DescriptorType_TABLE:
            /* Page table descriptor */
            desc = ((UInt64)attrs->type & 0x3) |
                   ((UInt64)phyAddr & 0xFFFFFFF000) |
                   ((UInt64)(attrs->pxnTable & 0x1) << 59) |
                   ((UInt64)(attrs->xnTable & 0x1) << 60) |
                   ((UInt64)(attrs->apTable & 0x3) << 61) |
                   ((UInt64)(attrs->nsTable & 0x1) << 63);
            break;
            
        /* Section descriptor */
        case Mmu_DescriptorType_BLOCK:
            desc = ((UInt64)attrs->type & 0x3) |
                   ((UInt64)(attrs->attrIndx & 0x7) << 2) |
                   ((UInt64)(attrs->nonSecure & 0x1) << 5) |
                   ((UInt64)(attrs->accPerm & 0x3) << 6) |
                   ((UInt64)(attrs->shareable & 0x3) << 8) |
                   ((UInt64)(attrs->accessFlag & 0x1) << 10) |
                   ((UInt64)(attrs->notGlobal & 0x1) << 11) |
                   ((UInt64)phyAddr & 0xFFC0000000) |
                   ((UInt64)(attrs->contiguous & 0x1) << 52) |
                   ((UInt64)(attrs->privNoExecute & 0x1) << 53) |
                   ((UInt64)(attrs->noExecute & 0x1) << 54) |
                   ((UInt64)(attrs->reserved & 0xF) << 55);
            break;
            
        default:
            Assert_isTrue(FALSE, Mmu_A_unknownDescType);
            break;
    }
    
    /* set the entry in the first level descriptor table */
    Mmu_module->firstLevelTableBuf[index] = desc;

    if (enabled) {
        /* if Mmu was enabled, then re-enable it */
        Mmu_enable();
    }
}


/*
 *  ======== Mmu_setSecondLevelDesc ========
 */
Void Mmu_setSecondLevelDesc(Ptr virtualAddr, UInt64 phyAddr,
                              Mmu_DescriptorAttrs *attrs)
{
    UInt32 index = (UInt32)virtualAddr >> LEVEL2_BLOCK_DESC_SHIFT;
    UInt64 desc = 0;
    Bool   enabled;

    /* Assert that attrs != NULL */
    Assert_isTrue(attrs != NULL, Mmu_A_nullPointer);

    /* determine the current state of the MMU */
    enabled = Mmu_isEnabled();

    /* disable the MMU (if already disabled, does nothing) */
    Mmu_disable();

    /* Determine which kind of descriptor. */
    switch (attrs->type) {
        case Mmu_DescriptorType_TABLE:
            /* Page table descriptor */
            desc = ((UInt64)attrs->type & 0x3) |
                   ((UInt64)phyAddr & 0xFFFFFFF000) |
                   ((UInt64)(attrs->pxnTable & 0x1) << 59) |
                   ((UInt64)(attrs->xnTable & 0x1) << 60) |
                   ((UInt64)(attrs->apTable & 0x3) << 61) |
                   ((UInt64)(attrs->nsTable & 0x1) << 63);
            break;

            /* Section descriptor */
        case Mmu_DescriptorType_BLOCK:
            desc = ((UInt64)attrs->type & 0x3) |
                   ((UInt64)(attrs->attrIndx & 0x7) << 2) |
                   ((UInt64)(attrs->nonSecure & 0x1) << 5) |
                   ((UInt64)(attrs->accPerm & 0x3) << 6) |
                   ((UInt64)(attrs->shareable & 0x3) << 8) |
                   ((UInt64)(attrs->accessFlag & 0x1) << 10) |
                   ((UInt64)(attrs->notGlobal & 0x1) << 11) |
                   ((UInt64)phyAddr & 0xFFFFE00000) |
                   ((UInt64)(attrs->contiguous & 0x1) << 52) |
                   ((UInt64)(attrs->privNoExecute & 0x1) << 53) |
                   ((UInt64)(attrs->noExecute & 0x1) << 54) |
                   ((UInt64)(attrs->reserved & 0xF) << 55);
            break;

        default:
            Assert_isTrue(FALSE, Mmu_A_unknownDescType);
            break;
    }

    /* set the entry in the first level descriptor table */
    Mmu_module->secondLevelTableBuf[index >> 9][index & 0x1FF] = desc;

    if (enabled) {
        /* if Mmu was enabled, then re-enable it */
        Mmu_enable();
    }
}
